#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
塑料市场数据分析器 - 使用阿里云百炼API

该脚本调用阿里云百炼大模型获取最新的塑料市场数据并进行专业分析，
支持流式输出思考过程和最终结果。

"""

import json
import logging
import argparse
import os
import sys
from datetime import datetime
from openai import OpenAI

# 设置控制台输出编码
# Windows系统需要特别处理
if sys.platform == 'win32':
    # 设置控制台输出编码为UTF-8
    import codecs
    if sys.stdout.encoding != 'UTF-8':
        sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer, 'strict')
    if sys.stderr.encoding != 'UTF-8':
        sys.stderr = codecs.getwriter('utf-8')(sys.stderr.buffer, 'strict')

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(message)s', force=True)

class BailianMarketAnalyzer:
    """
    使用阿里云百炼大模型直接获取和分析塑料市场数据
    """
    
    def __init__(self, api_key=None, show_reasoning=True):
        """
        初始化分析器
        
        Args:
            api_key (str, optional): 百炼API密钥，如果不提供，将尝试从环境变量获取
            show_reasoning (bool, optional): 是否显示模型思考过程，默认为True
        """
        # 如果未提供API密钥，尝试从环境变量获取
        self.api_key = api_key or os.environ.get("DASHSCOPE_API_KEY")
        if not self.api_key:
            raise ValueError("API密钥未提供，请通过参数传入或设置DASHSCOPE_API_KEY环境变量")
            
        # API配置
        try:
            # 创建客户端时不传入任何额外参数，避免proxies错误
            self.client = OpenAI(
                api_key=self.api_key,
                base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
            )
            
            # 清除可能存在的代理环境变量，避免它们影响客户端
            for proxy_var in ['HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy']:
                if proxy_var in os.environ:
                    logging.warning(f"检测到代理环境变量 {proxy_var}，这可能会干扰API连接")
                    
            logging.info("成功初始化API客户端")
        except TypeError as e:
            if 'proxies' in str(e):
                # 如果错误与proxies参数有关，尝试另一种初始化方式
                logging.warning("使用标准初始化方式失败(proxies错误)，尝试替代方法")
                import httpx
                # 创建一个不使用代理的httpx客户端
                http_client = httpx.Client(
                    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
                    timeout=60.0
                )
                self.client = OpenAI(
                    api_key=self.api_key,
                    http_client=http_client
                )
                logging.info("使用替代方法成功初始化API客户端")
            else:
                # 其他类型错误，重新抛出
                raise
        
        self.model_id = "qwq-32b"  # 默认使用百炼qwq-32b模型
        self.show_reasoning = show_reasoning
    
    def validate_data_date(self, market_data, expect_current=True):
        """
        验证市场数据的日期是否符合要求
        
        Args:
            market_data (dict): 市场数据字典
            expect_current (bool): 是否期望数据为当前日期
            
        Returns:
            bool: 数据日期是否有效
        """
        if not market_data or 'date' not in market_data:
            logging.warning("数据中缺少日期字段")
            print("\n⚠️ 警告: 返回的数据中缺少日期字段，可能不是最新数据")
            return False
            
        # 获取当前日期（不带时间）
        current_date = datetime.now().strftime("%Y年%m月%d日")
        current_year = datetime.now().year
        current_month = datetime.now().month
        data_date = market_data['date']
        
        # 检查数据日期
        if expect_current:
            # 检查是否包含过期年份
            outdated = False
            
            # 检查是否有明确的2023年或其他过期年份
            if "2023年" in data_date or "2022年" in data_date or "2021年" in data_date:
                outdated = True
                logging.warning(f"数据包含过期年份: {data_date}")
            
            # 检查是否有数字形式的过期年份（如2023-10-20）
            elif any(str(year) in data_date for year in range(2000, current_year)):
                outdated = True
                logging.warning(f"数据可能包含过期年份（数字格式）: {data_date}")
                
            # 检查是否与当前年月匹配
            current_year_str = f"{current_year}年"
            current_month_str = f"{current_month}月"
            if not (current_year_str in data_date and (current_month_str in data_date or f"{current_month-1}月" in data_date)):
                # 如果不包含当前年月，再次检查是否有数字形式
                current_year_month_pattern = f"{current_year}-{current_month:02d}"
                prev_month_pattern = f"{current_year}-{current_month-1:02d}" if current_month > 1 else f"{current_year-1}-12"
                if not (current_year_month_pattern in data_date or prev_month_pattern in data_date):
                    logging.warning(f"数据日期与当前年月不匹配: {data_date}")
                    if not outdated:  # 避免重复警告
                        print(f"\n⚠️ 警告: 返回的数据日期 {data_date} 可能不是最新数据")
                        outdated = True
            
            if outdated:
                print(f"⚠️ 警告: 返回的数据日期为 {data_date}，而不是期望的 {current_date}")
                print("⚠️ 这可能是因为模型返回了历史数据而非最新数据。")
                print("⚠️ 建议添加 --force-current-date 参数强制要求当前日期。")
                return False
                
        # 输出数据日期信息
        logging.info(f"数据日期: {data_date}")
        return True

    def fetch_market_data(self, force_current_date=False):
        """
        获取截止到今天最新的塑料市场价格数据
        
        Args:
            force_current_date (bool): 是否在提示中强调当前日期
            
        Returns:
            dict: 包含各类塑料产品价格数据的字典，如果获取失败则返回空字典
        """
        # 获取当前日期
        current_date = datetime.now().strftime("%Y年%m月%d日")
        
        # 构建提示信息，让模型返回结构化的市场数据
        if force_current_date:
            date_emphasis = f"""
极其重要 - 关于日期的强制要求：
1. 你必须使用{current_date}的最新数据，绝对不要使用2023年或历史数据
2. 如果你没有{current_date}的准确数据，请基于最新的市场趋势进行合理预估
3. 在你的回复中，数据日期必须标注为{current_date}
4. 这是一个时效性极强的商业分析，使用过时数据将导致错误决策
5. 再次强调：不要提供2023年的数据，即使你认为那些数据更可靠

"""
        else:
            date_emphasis = ""
        
        prompt = f"""请以专业的塑料市场数据提供者的身份，提供截止到{current_date}（今天）的最新实时塑料市场数据。

{date_emphasis}非常重要：
1. 必须提供{current_date}（今天）的最新数据，不要提供2023年或其他过期的历史数据
2. 如果你不确定最新价格，请基于当前市场趋势和最近的价格波动进行合理预估
3. 请确保数据时效性，标明数据更新日期为{current_date}
4. 这些数据将用于商业决策，请务必提供最准确的当前市场情况

请提供中国市场以下塑料种类的最新价格数据：
1. PC (聚碳酸酯)
2. ABS (丙烯腈-丁二烯-苯乙烯共聚物)
3. PA66 (聚酰胺66)
4. PA6 (聚酰胺6)
5. POM (聚甲醛)
6. PMMA (聚甲基丙烯酸甲酯)
7. HIPS (高抗冲聚苯乙烯)
8. GPPS (通用级聚苯乙烯)

要求：
- 返回JSON格式的结构化数据
- 每种类别至少包含3种主流型号
- 包含最新价格和近一个月变化率
- 标明生产商和市场区域
- 数据更新日期必须是{current_date}

数据结构应如下所示:
{{
  "date": "{current_date}",  // 必须是今天的日期
  "products": [
    {{
      "category": "产品类别(如PC、ABS等)",
      "model": "型号",
      "manufacturer": "生产商",
      "price": "价格(元/吨)",
      "change": "变化率(%)",
      "region": "市场区域"
    }},
    // 更多产品...
  ]
}}

再次强调：请确保提供{current_date}的最新数据，不要提供2023年10月或其他过期的数据。请直接返回JSON数据，不要有任何其他解释性文字。"""
        
        logging.info("正在请求塑料市场价格数据...")
        print("\n" + "="*80)
        print("正在获取塑料市场价格数据...")
        print("="*80)
        
        try:
            # 创建聊天完成请求
            completion = self.client.chat.completions.create(
                model=self.model_id,
                messages=[
                    {"role": "user", "content": prompt}
                ],
                stream=True
            )
            
            # 用于存储完整响应
            reasoning_content = ""  # 思考过程
            answer_content = ""     # 最终回复
            is_answering = False    # 是否已开始回复
            
            if self.show_reasoning:
                print("\n" + "=" * 20 + "思考过程" + "=" * 20 + "\n")
            
            # 处理流式响应
            for chunk in completion:
                # 如果chunk.choices为空，处理usage信息
                if not chunk.choices:
                    if hasattr(chunk, 'usage'):
                        logging.info(f"Token使用: {chunk.usage}")
                else:
                    delta = chunk.choices[0].delta
                    
                    # 处理思考过程
                    if hasattr(delta, 'reasoning_content') and delta.reasoning_content is not None:
                        if self.show_reasoning:
                            print(delta.reasoning_content, end='', flush=True)
                        reasoning_content += delta.reasoning_content
                    else:
                        # 处理回复内容
                        if delta.content != "" and is_answering is False:
                            if self.show_reasoning:
                                print("\n" + "=" * 20 + "市场数据" + "=" * 20 + "\n")
                            else:
                                print()  # 添加一个空行
                            is_answering = True
                        
                        if hasattr(delta, 'content') and delta.content is not None:
                            print(delta.content, end='', flush=True)
                            answer_content += delta.content
            
            print("\n" + "="*80)
            
            # 尝试解析JSON
            try:
                # 查找JSON部分
                json_start = answer_content.find('{')
                json_end = answer_content.rfind('}') + 1
                
                if json_start >= 0 and json_end > json_start:
                    # 提取JSON部分
                    json_text = answer_content[json_start:json_end]
                    market_data = json.loads(json_text)
                    
                    # 验证数据结构
                    if 'products' in market_data and isinstance(market_data['products'], list):
                        product_count = len(market_data['products'])
                        logging.info(f"成功获取{product_count}条产品数据")
                        
                        # 验证数据日期
                        self.validate_data_date(market_data)
                        
                        return market_data
                    else:
                        logging.error("JSON结构不符合预期，缺少'products'字段或非列表格式")
                        return {}
                else:
                    logging.error("未找到有效的JSON内容")
                    return {}
            except json.JSONDecodeError as e:
                logging.error(f"JSON解析错误: {str(e)}")
                return {}
                
        except Exception as e:
            logging.error(f"请求失败: {str(e)}")
            return {}
    
    def analyze_market(self, market_data=None, force_current_date=False):
        """
        调用大模型分析塑料粒子市场数据和趋势
        
        Args:
            market_data (dict, optional): 市场数据，如果不提供，会先调用fetch_market_data获取
            force_current_date (bool): 是否在提示中强调当前日期
            
        Returns:
            tuple: (market_data, analysis_text) - 市场数据和分析报告，如果获取失败则返回空
        """
        # 获取当前日期
        current_date = datetime.now().strftime("%Y年%m月%d日")
        
        # 如果未提供市场数据，先获取数据
        if market_data is None:
            market_data = self.fetch_market_data(force_current_date)
            
        # 如果无法获取市场数据，返回空
        if not market_data:
            logging.warning("无法获取市场数据，无法进行分析")
            return {}, ""
            
        # 将市场数据转换为格式化的字符串
        market_data_str = json.dumps(market_data, ensure_ascii=False, indent=2)
        
        # 添加日期强调
        if force_current_date:
            date_emphasis = f"""
极其重要 - 关于分析日期的要求：
1. 你的分析必须基于{current_date}的最新市场状况
2. 不要使用你训练数据中的历史信息来替代或补充这些数据
3. 如果发现数据日期不是{current_date}，请在分析开头明确指出这一问题
4. 所有市场预测和建议必须以{current_date}为起点，而不是任何过去的日期

"""
        else:
            date_emphasis = ""
        
        # 构建提示信息，让模型分析市场数据和趋势
        prompt = f"""请作为专业的塑料粒子市场分析师，根据以下提供的{current_date}（今天）最新市场数据进行全面分析：

```json
{market_data_str}
```

{date_emphasis}非常重要：
1. 请确保你的分析基于这些最新数据，而不是使用你训练数据中的旧信息
2. 分析应反映当前({current_date})的市场状况，不要使用2023年或更早的过时市场评估
3. 如果数据中的日期与今天({current_date})不符，请在分析开头指出这一点

请提供以下分析：
1. 整体市场趋势概述 - 重点关注当前价格走势和近期变化
2. 各类别产品的价格表现详细分析 - 突出近期表现最好和最差的品类
3. 价格区间分布情况 - 提供数据驱动的见解
4. 主要生产商的市场表现分析 - 基于提供的最新数据
5. 未来3-6个月(从{current_date}开始)的市场预测 - 考虑当前特殊市场因素
6. 对采购商和投资者的实用建议 - 针对当前市场状况

请使用Markdown格式，提供专业、深入且有数据支持的分析报告。确保你的分析是基于提供的数据，而不是使用过时的信息。"""
        
        logging.info("正在请求市场分析...")
        print("\n" + "="*80)
        print("正在分析塑料市场数据...")
        print("="*80)
        
        try:
            # 创建聊天完成请求
            completion = self.client.chat.completions.create(
                model=self.model_id,
                messages=[
                    {"role": "user", "content": prompt}
                ],
                stream=True
            )
            
            # 用于存储完整响应
            reasoning_content = ""  # 思考过程
            analysis_text = ""      # 分析报告
            is_answering = False    # 是否已开始回复
            
            if self.show_reasoning:
                print("\n" + "=" * 20 + "思考过程" + "=" * 20 + "\n")
            
            # 处理流式响应
            for chunk in completion:
                # 如果chunk.choices为空，处理usage信息
                if not chunk.choices:
                    if hasattr(chunk, 'usage'):
                        logging.info(f"Token使用: {chunk.usage}")
                else:
                    delta = chunk.choices[0].delta
                    
                    # 处理思考过程
                    if hasattr(delta, 'reasoning_content') and delta.reasoning_content is not None:
                        if self.show_reasoning:
                            print(delta.reasoning_content, end='', flush=True)
                        reasoning_content += delta.reasoning_content
                    else:
                        # 处理回复内容
                        if delta.content != "" and is_answering is False:
                            if self.show_reasoning:
                                print("\n" + "=" * 20 + "市场分析报告" + "=" * 20 + "\n")
                            else:
                                print()  # 添加一个空行
                            is_answering = True
                        
                        if hasattr(delta, 'content') and delta.content is not None:
                            print(delta.content, end='', flush=True)
                            analysis_text += delta.content
            
            print("\n" + "="*80)
            
            return market_data, analysis_text
                
        except Exception as e:
            logging.error(f"分析请求失败: {str(e)}")
            return market_data, ""
    
    def save_results(self, market_data, analysis_text):
        """
        保存市场数据和分析结果到文件
        
        Args:
            market_data (dict): 市场数据字典
            analysis_text (str): 分析报告文本
            
        Returns:
            tuple: (data_filename, report_filename) - 保存的文件名
        """
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        
        # 保存原始市场数据
        data_filename = f"market_data_{timestamp}.json"
        if market_data:
            with open(data_filename, 'w', encoding='utf-8-sig') as f:
                json.dump(market_data, f, ensure_ascii=False, indent=2)
            logging.info(f"市场数据已保存到 {data_filename}")
            print(f"市场数据已保存到 {data_filename}")
        else:
            data_filename = None
            logging.warning("没有市场数据可保存")
        
        # 保存分析报告
        report_filename = f"market_analysis_{timestamp}.md"
        if analysis_text:
            with open(report_filename, 'w', encoding='utf-8') as f:
                f.write(f"# 塑料市场分析报告\n\n")
                f.write(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
                f.write(analysis_text)
            logging.info(f"分析报告已保存到 {report_filename}")
            print(f"分析报告已保存到 {report_filename}")
        else:
            report_filename = None
            logging.warning("没有分析报告可保存")
            
        return data_filename, report_filename

def main():
    """
    主函数，解析命令行参数并执行市场分析
    """
    parser = argparse.ArgumentParser(description="塑料市场数据分析器 - 使用阿里云百炼API")
    parser.add_argument("--api-key", help="百炼API密钥")
    parser.add_argument("--save", action="store_true", help="保存结果到文件")
    parser.add_argument("--data-only", action="store_true", help="仅获取市场数据，不进行分析")
    parser.add_argument("--model", default="qwq-32b", help="指定使用的模型，默认为qwq-32b")
    parser.add_argument("--no-reasoning", action="store_true", help="不显示模型思考过程")
    parser.add_argument("--force-current-date", action="store_true", help="强制要求模型使用当前日期的数据")
    args = parser.parse_args()
    
    try:
        # 初始化分析器
        analyzer = BailianMarketAnalyzer(
            api_key=args.api_key, 
            show_reasoning=not args.no_reasoning
        )
        
        # 设置模型
        analyzer.model_id = args.model
        
        if args.data_only:
            # 仅获取市场数据
            market_data = analyzer.fetch_market_data(force_current_date=args.force_current_date)
            analysis_text = ""
            
            if not market_data:
                print("\n没有获取到市场数据")
                return 1
        else:
            # 获取市场数据并进行分析
            market_data, analysis_text = analyzer.analyze_market(force_current_date=args.force_current_date)
            
            if not market_data and not analysis_text:
                print("\n没有获取到市场数据和分析结果")
                return 1
        
        # 保存结果（如果需要）
        if args.save and (market_data or analysis_text):
            analyzer.save_results(market_data, analysis_text)
        
    except Exception as e:
        error_msg = f"程序运行错误: {str(e)}"
        logging.error(error_msg)
        print(f"\n错误: {error_msg}")
        
        import traceback
        logging.error(f"堆栈跟踪: {traceback.format_exc()}")
        
        return 1
    
    return 0

if __name__ == "__main__":
    exit(main()) 